package bma.common.langutil.io;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import bma.common.langutil.concurrent.ScheduledExecutor;
import bma.common.langutil.core.ToStringUtil;

/**
 * 文件检查器
 * 
 * @author guanzhong
 * 
 */
public class FileMonitor {

	final org.slf4j.Logger log = org.slf4j.LoggerFactory
			.getLogger(FileMonitor.class);

	public static enum Event {
		NEW, CHANGE, DELETE
	}

	public static class FileStatus {
		public String fileName;
		public boolean init;
		public boolean exists;
		public long lastModified;

		@Override
		public String toString() {
			return ToStringUtil.fieldReflect(this);
		}
	}

	private volatile boolean stoped;
	private ScheduledExecutor executor;
	private long time = 1000;
	private boolean notifyInitEvent = false;

	private List<FileMonitorListener> listeners = new LinkedList<FileMonitorListener>();
	private List<String> files = new LinkedList<String>();
	private transient List<FileStatus> fileStatus = new LinkedList<FileStatus>();

	public boolean isNotifyInitEvent() {
		return notifyInitEvent;
	}

	public void setNotifyInitEvent(boolean notifyInitEvent) {
		this.notifyInitEvent = notifyInitEvent;
	}

	public ScheduledExecutor getExecutor() {
		return executor;
	}

	public void setExecutor(ScheduledExecutor executor) {
		this.executor = executor;
	}

	public long getTime() {
		return time;
	}

	public void setTime(long time) {
		this.time = time;
	}

	public List<FileMonitorListener> getListeners() {
		return listeners;
	}

	public void setListeners(List<FileMonitorListener> listeners) {
		this.listeners.clear();
		this.listeners.addAll(listeners);
	}

	public void addListener(FileMonitorListener lis) {
		this.listeners.add(lis);
	}

	public void setListener(FileMonitorListener lis) {
		this.listeners.add(lis);
	}

	public List<String> getFiles() {
		return files;
	}

	public void setFiles(List<String> files) {
		this.files.clear();
		this.files.addAll(files);
	}

	public void setFile(String file) {
		this.files.add(file);
	}

	public void start() {
		for (String f : files) {
			FileStatus fs = new FileStatus();
			fs.fileName = f;
			fileStatus.add(fs);
		}
		this.executor.scheduleWithFixedDelay(new Runnable() {
			@Override
			public void run() {
				checkFiles();
			}
		}, time, time, TimeUnit.MILLISECONDS);
	}

	public void stop() {
		stoped = true;
	}

	public void checkFiles() {
		if (stoped)
			return;
		for (FileStatus f : this.fileStatus) {
			try {
				checkFile(f);
			} catch (IOException e1) {
				if (log.isDebugEnabled()) {
					log.debug("checkFile(" + f + ") fail ", e1);
				}
			} catch (Exception e2) {
				if (log.isWarnEnabled()) {
					log.warn("checkFile(" + f + ") fail", e2);
				}
			}
		}
	}

	public void checkFile(FileStatus fs) throws IOException {
		File file = new File(fs.fileName);
		if (!file.exists()) {
			boolean tmp = fs.exists;
			fs.exists = false;
			if (tmp) {
				triggerEvent(fs, Event.DELETE);
			}
			return;
		}
		long tm = file.lastModified();
		if (!fs.exists) {
			fs.exists = true;
			fs.lastModified = tm;
			if (!fs.init) {
				fs.init = true;
				if (!notifyInitEvent) {
					return;
				}
			}
			triggerEvent(fs, Event.NEW);
			return;
		}
		if (fs.lastModified == 0) {
			fs.lastModified = tm;
			return;
		}
		if (fs.lastModified < tm) {
			fs.lastModified = tm;
			triggerEvent(fs, Event.CHANGE);
			return;
		}
	}

	public void triggerEvent(FileStatus fs, Event event) {
		for (FileMonitorListener lis : this.listeners) {
			try {
				lis.handleEvent(fs.fileName, event);
			} catch (Exception e) {
				if (log.isDebugEnabled()) {
					log.debug("listener(" + lis + ") handle event(" + event
							+ ") fail ", e);
				}
			}
		}
	}
}
